#![allow(non_snake_case)]
mod input_number;
mod text_area;

pub use self::input_number::InputNumber;
pub use self::text_area::{TextArea, TextAreaRef};

use leptos::{ev, html, prelude::*};
use shq_common::prelude::Size;
use thaw_utils::{class_list, ArcOneCallback, BoxOneCallback, ComponentRef, Model};

use crate::ValidType;

#[derive(Default, Clone)]
pub enum InputType {
    #[default]
    Text,
    Password,
}
impl InputType {
    pub fn as_str(&self) -> &'static str {
        match self {
            InputType::Text => "text",
            InputType::Password => "password",
        }
    }
}
#[slot]
pub struct InputPrefix {
    #[prop(default = true)]
    if_: bool,
    children: Children,
}

#[slot]
pub struct InputSuffix {
    #[prop(default = true)]
    if_: bool,
    children: Children,
}

#[component]
pub fn Input(
    #[prop(optional, into)] value: Model<Option<String>>,
    #[prop(optional, into)] allow_value: Option<ArcOneCallback<String, bool>>,
    #[prop(optional, into)] input_type: MaybeProp<InputType>,
    #[prop(optional, into)] placeholder: MaybeProp<String>,
    #[prop(optional, into)] on_change: Option<BoxOneCallback<Option<String>>>,
    #[prop(optional, into)] on_focus: Option<BoxOneCallback<ev::FocusEvent>>,
    #[prop(optional, into)] on_blur: Option<BoxOneCallback<ev::FocusEvent>>,
    #[prop(optional, into)] disabled: MaybeProp<bool>,
    #[prop(optional, into)] size: MaybeProp<Size>,
    #[prop(optional, into)] valid_type: MaybeProp<ValidType>,
    #[prop(default=None)] input_prefix: Option<InputPrefix>,
    #[prop(default=None)] input_suffix: Option<InputSuffix>,
    #[prop(optional)] comp_ref: ComponentRef<InputRef>,
    #[prop(default=true.into(), into)] border: MaybeProp<bool>,
    #[prop(optional)] inner_max_width: u32,
    #[prop(optional)] is_init_focus: bool,
) -> impl IntoView {
    let on_input = move |ev:web_sys::Event| {
        let input_value = event_target_value(&ev);
        if let Some(allow_value) = allow_value.as_ref() {
            if !allow_value(input_value.clone()) {
                value.set(value.get());
                return;
            }
        }
        let val = if input_value.is_empty() {
            None
        } else {
            Some(input_value)
        };
        if let Some(change) = on_change.as_ref() {
            change(val.clone());
        }
        value.set(val);
    };
    let is_focus = RwSignal::new(false);
    let on_internal_focus = move |ev| {
        is_focus.set(true);
        if let Some(on_focus) = on_focus.as_ref() {
            on_focus(ev);
        }
    };
    let on_internal_blur = move |ev| {
        is_focus.set(false);
        if let Some(on_blur) = on_blur.as_ref() {
            on_blur(ev);
        }
    };

    let input_ref = NodeRef::new();
    input_ref.on_load(move |_| {
        comp_ref.load(InputRef { input_ref });
    });

    Effect::new(
        move || {
            if let Some(input_el) = input_ref.get_untracked() {
                if is_init_focus {
                    _ = input_el.focus();
                }
            }
        }
    );

    // let on_mousedown = move |event: ev::MouseEvent| {
    //     let el: web_sys::HtmlElement = event_target(&event);
    //     if el.tag_name() != "INPUT" {
    //         event.prevent_default();
    //         if !is_focus.get_untracked() {
    //             if let Some(comp_ref) = comp_ref.get_untracked() {
    //                 comp_ref.focus();
    //             }
    //         }
    //     }
    // };
    let has_border = border.get().unwrap_or(false);
    let has_fix = || input_prefix.is_some() || input_suffix.is_some();
    let size_class=move||size.get().map(|s|s.to_class("input")).unwrap_or("".to_string());
    let label_class = move || {
        if has_border {
            let color = valid_type.get().map(|vt| vt.as_color_str()).unwrap_or("");
            if color.is_empty() {
                format!("input input-bordered {}",size_class())
            } else {
                format!("input input-bordered input-{} {}",color,size_class())
            }
        } else {
            "".to_owned()
        }
    };
    let input_view = move |class: String| {
        let style_width = if inner_max_width > 0 {
            format!("max-width:{}px", inner_max_width)
        } else {
            "".to_owned()
        };
        view! {
            <input
                type=move || input_type.get().map(|it|it.as_str()).unwrap_or("text")
                disabled=move || disabled.get()
                prop:value=move || {
                    value.get().unwrap_or("".to_owned())
                }
                on:input=on_input
                on:focus=on_internal_focus
                on:blur=on_internal_blur
                class=class_list![class,("outline-0 border-0 w-full ml-1 mr-1 bg-inherit",move||!has_border)]
                style=style_width
                placeholder=placeholder.get()
                node_ref=input_ref
            />
        }
    };
    view! {
        {if has_fix() {
            view! {
                <label class=class_list!["flex items-center gap-2 max-w-xs",label_class,size_class]>
                    {if let Some(prefix) = input_prefix.and_then(|prefix| prefix.if_.then_some(prefix)) {
                        view! { {(prefix.children)()} }.into()
                    } else {
                        None
                    }}
                    {input_view("grow".to_owned())}
                    {if let Some(suffix) = input_suffix.and_then(|suffix| suffix.if_.then_some(suffix)) {
                        view! { {(suffix.children)()} }.into()
                    } else {
                        None
                    }}
                </label>
            }.into_any()
        } else {
            input_view(format!("w-full max-w-xs {}",label_class())).into_any()
        }}
    }
}

#[derive(Clone)]
pub struct InputRef {
    pub input_ref: NodeRef<html::Input>,
}

impl InputRef {
    pub fn new(input_ref: NodeRef<html::Input>) -> Self {
        Self { input_ref }
    }
    pub fn focus(&self) {
        if let Some(input_el) = self.input_ref.get_untracked() {
            _ = input_el.focus();
        }
    }

    pub fn blur(&self) {
        if let Some(input_el) = self.input_ref.get_untracked() {
            _ = input_el.blur();
        }
    }
}
